package com.imdada.open.platform.callback;

import com.alibaba.fastjson.JSON;
import com.imdada.open.platform.callback.internal.CallbackParam;
import com.imdada.open.platform.callback.internal.DaDaCallbackStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;

import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 达达订单状态变更回调listener<br/>
 * 在订单状态变更的时候(比如订单创建成功、骑士接单、到店、取货、送达等)<br/>
 * 达达都会通过发单参数里面的callback字段对应的地址来通知商户<br/>
 * 文档地址：<a href="http://newopen.imdada.cn/#/development/file/order">http://newopen.imdada.cn/#/development/file/order</a>
 */
@Slf4j
public class DaDaCallbackListener {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        // 骑士到店消息示例
        String arriveShopCallbackStr = "{\"signature\":\"2b3aa825562bcea9471fe860b3f8a8fa\",\"client_id\":\"1357061741749469184\",\"order_id\":\"Cu16538768403935339353\",\"order_status\":100,\"cancel_reason\":\"\",\"cancel_from\":0,\"dm_id\":170900,\"dm_name\":\"张三\",\"dm_mobile\":\"18500000000\",\"update_time\":1653876860,\"transporter_lng\":121.526008,\"transporter_lat\":31.220394,\"transporter_type\":1,\"is_finish_code\":false}";
        onReceiveMsg(arriveShopCallbackStr);

        // 订单创建失败消息示例
        String orderCreateFailedCallbackStr = "{\"signature\":\"cd6339c2b4f481d5515831d53ff43ccc\",\"client_id\":\"\",\"order_id\":\"948220531221045216\",\"order_status\":1000,\"cancel_reason\":\"您所选的发货地址和收货地址处于疫情封控中，达达骑⼠将⽆法为您提供服务\",\"cancel_from\":3,\"dm_id\":0,\"dm_name\":\"\",\"dm_mobile\":\"\",\"update_time\":1654006323146}";
        onReceiveMsg(orderCreateFailedCallbackStr);
    }

    /**
     * 收到回调消息时的回调
     *
     * @param dadaCallbackStr 达达回调原始参数
     */
    public static void onReceiveMsg(String dadaCallbackStr) {
        // json字符串转对象
        CallbackParam callbackParam = JSON.parseObject(dadaCallbackStr, CallbackParam.class);

        // 验证签名
        verifySignature(callbackParam);

        // 签名通过，进行自己的业务逻辑处理
        doBizLogic(callbackParam);
    }

    /**
     * 执行商户自己的回调业务逻辑(比如落表)
     * @param callbackParam
     */
    private static void doBizLogic(CallbackParam callbackParam) {
        log.info("收到达达回调, 订单号: {}, 达达单号: {}, 订单状态: {}, 时间: {}, 骑士id: {}, 骑士姓名: {}, 骑士手机号: {}",
                callbackParam.getOrderId(),
                callbackParam.getClientId(),
                callbackParam.getOrderStatus(),
                parseUpdateTime(callbackParam),
                callbackParam.getDmId(),
                callbackParam.getDmName(),
                callbackParam.getDmMobile()
        );
    }

    /**
     * 解析更新时间(除了达达创建运单失败=1000的情况下单位是毫秒，其他情况下单位是秒)
     *
     * @param callbackParam
     * @return
     */
    private static String parseUpdateTime(CallbackParam callbackParam) {
        Long updateTime = callbackParam.getUpdateTime();
        if (Objects.equals(callbackParam.getOrderStatus(), DaDaCallbackStatusEnum.ORDER_CREATE_FAILED.getOrderStatusCode())) {
            // 达达创建运单失败=1000的情况下单位是毫秒
            return Instant.ofEpochMilli(updateTime).atZone(ZoneId.systemDefault()).toLocalDateTime().format(FORMATTER);
        } else {
            // 其他情况下单位是秒
            return Instant.ofEpochSecond(updateTime).atZone(ZoneId.systemDefault()).toLocalDateTime().format(FORMATTER);
        }
    }

    /**
     * 验证签名是否正确
     */
    private static void verifySignature(CallbackParam callbackParam) {
        // 根据参数计算出期望的正确签名
        String expectSign = generateSignature(callbackParam);

        // 实际收到的签名
        String actualSign = callbackParam.getSignature();

        // 期望签名结果和实际签名结果比较
        if (!Objects.equals(expectSign, actualSign)) {
            throw new IllegalArgumentException("签名校验不通过");
        }
    }

    /**
     * 根据相关参数计算回调的签名(步骤参考文档【回调签名生成过程】:http://newopen.imdada.cn/#/development/file/order)
     */
    private static String generateSignature(CallbackParam callbackParam) {
        String clientId = callbackParam.getClientId();
        String orderId = callbackParam.getOrderId();
        Long updateTime = callbackParam.getUpdateTime();

        // 将签名相关字段加入list
        List<String> list = new ArrayList<>(4);
        list.add(clientId == null ? "" : clientId);
        list.add(orderId == null ? "" : orderId);
        list.add(updateTime == null ? "" : updateTime.toString());

        // 将参与签名的字段的值进行升序排列
        Collections.sort(list);

        // 将排序过后的参数，进行字符串拼接
        String joinedStr = String.join("", list);

        // 对拼接后的字符串进行md5加密
        return DigestUtils.md5Hex(joinedStr);
    }

}
